home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / ISSUE20 / CACHE / CachedCalendar.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1997-02-06  |  10.9 KB  |  348 lines

  1. unit CachedCalendar;
  2. {
  3.   Author : Neil McClements
  4.   Date   : January '97
  5.   C/right: (c) 1997 N. McClements
  6.   Purpose: A data-aware calendar with the ability to cache dates and occasions
  7.            using file streams
  8. }
  9.  
  10. interface
  11.  
  12. uses
  13.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  14.   Grids, Calendar,DBCtrls, DBTables, DB, Menus;
  15.  
  16. //
  17. //  TSpecialDateList is used to hold each date and its occasion. It would be
  18. //  possible to store multiplke occasions against the same date by altering the
  19. //  properties Date and Occasion to use TStringLists or other data structure.
  20. //
  21.  
  22. type TSpecialDateList=class(TComponent)
  23.      private
  24.         FDate:TDatetime;
  25.         FOccasion:string;
  26.      published
  27.         property Date:TDatetime read FDate write FDate;
  28.         property Occasion:string read FOccasion write FOccasion;
  29. end;
  30.  
  31. //
  32. //  TCachedCalendar - data-aware calendar that can be wired up to a datasource.
  33. //  Two fields are used to retrieve dates and occasions - DateField and TextField.
  34. //
  35.  
  36.  
  37. type
  38.   TCachedCalendar = class(TCalendar)
  39.   private
  40.     FConfigFile:string;
  41.     FUseCache:boolean;
  42.     FDateFieldDataLink:TFieldDataLink;
  43.     FTextFieldDataLink:TFieldDataLink;
  44.     FDateList:TList;
  45.     FDatePopupMenu:TPopupMenu;
  46.     procedure DataChange(Sender:TObject);
  47.     function  GetDataSource:TDataSource;
  48.     function  GetDateField:string;
  49.     function  GetTextField:string;
  50.     procedure SetDataSource(theSource:TDataSource);
  51.     procedure SetDateField(const theFieldName:string);
  52.     procedure SetTextField(const theFieldName:string);
  53.   protected
  54.     procedure Click; override;
  55.     procedure DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState); override;
  56.     procedure Notification(AComponent: TComponent; Operation: TOperation);
  57.     function  ReadDatesFromStream:boolean;
  58.     procedure SetUseCache(CacheOnOff:boolean);
  59.     function  WriteDatesToStream:boolean;
  60.   public
  61.     constructor Create(Owner:TComponent);override;
  62.     destructor  Destroy; override;
  63.     function    Refresh:boolean;
  64.   published
  65.     property ConfigFile:string read FConfigFile write FConfigFile;
  66.     property DataSource:TDataSource read GetDataSource write SetDataSource;
  67.     property DateField:string read GetDateField write SetDateField;
  68.     property TextField:string read GetTextField write SetTextField;
  69.     property UseCache:boolean read FUseCache write SetUseCache default false;
  70.   end;
  71.  
  72. procedure Register;
  73.  
  74. implementation
  75.  
  76. procedure Register;
  77. begin
  78.   RegisterComponents('more...', [TCachedCalendar]);
  79. end;
  80.  
  81. constructor TCachedCalendar.Create(Owner:TComponent);
  82. var
  83.    exePath:string;
  84. begin
  85.      inherited Create(Owner);
  86.  
  87.      // Configure the two data links required - one for the "special date" field
  88.      // and the other for the occasion text (eg "New Year's Day")
  89.  
  90.      FDateFieldDataLink:=TFieldDataLink.Create;
  91.      FTextFieldDataLink:=TFieldDataLink.Create;
  92.      FDateFieldDataLink.OnDataChange:=DataChange;
  93.      FTextFieldDataLink.OnDataChange:=DataChange;
  94.  
  95.      // Default config file used to cache dates between application sessions
  96.  
  97.      if FConfigFile='' then
  98.        begin
  99.           exePath:=ExtractFilePath(application.exename);
  100.    // ### self.name could also be used if two or more calendars were going to be used in the same application          
  101.           FConfigFile:=exePath+'Calendar.cfg';
  102.        end;
  103.  
  104.      // Register the date list component so Delphi knows how to handle it in file streams
  105.  
  106.      RegisterClass(TSpecialDateList);
  107.  
  108.      // Other defaults...
  109.  
  110.      FDateList:=TList.Create;
  111.      FUseCache:=false;
  112. end;
  113.  
  114. destructor TCachedCalendar.Destroy;
  115. begin
  116.      // Destroy the data links, component member variables...
  117.  
  118.      if assigned(FDateFieldDataLink) then
  119.         FDateFieldDataLink.free;
  120.      if assigned(FTextFieldDataLink) then
  121.         FTextFieldDataLink.free;
  122.      FDateList.free;
  123.  
  124.      // ... and finally call the ancestor's destructor
  125.  
  126.      inherited Destroy;
  127. end;
  128.  
  129. procedure TCachedCalendar.SetUseCache(CacheOnOff:boolean);
  130. begin
  131.   // Refresh the calendar whenever the developer switches the calendar mode from
  132.   // cached to not-cached
  133.  
  134.   if (CacheOnOff=true) then
  135.       ReadDatesFromStream
  136.   else
  137.       Refresh;
  138. end;
  139.  
  140. function TCachedCalendar.Refresh:boolean;
  141. var
  142.    dSet:TDataset;
  143.    theDate:TDateTime;
  144.    theOccasion:string;
  145.    DateInfo:TSpecialDateList;
  146. begin
  147. // Using the data links already configured, iterate through the result set
  148. // gathering the special dates and occasions
  149.  
  150. if (assigned(FDateFieldDataLink) and (DataSource<>nil)) then
  151.   begin
  152.     FDateList.clear;
  153.     dSet:=DataSource.Dataset;
  154.     dSet.Active:=true;
  155.     dSet.first;
  156.     while not dSet.eof do
  157.       begin
  158.          DateInfo:=TSpecialDateList.create(Owner);
  159.          DateInfo.Date:=dSet.FieldByName(FDateFieldDataLink.FieldName).AsDatetime;
  160.          DateInfo.Occasion:=dSet.FieldByName(FTextFieldDataLink.FieldName).AsString;
  161.          // Add the dates to the member list - this is used later in DrawCell
  162.          FDateList.add(DateInfo);
  163.          dSet.next;
  164.       end;
  165.     // Record the updated details in the cache for next time...
  166.     Result:=WriteDatesToStream;
  167.   end
  168. else
  169.   Result:=false;
  170. Invalidate;   // Ensure the newly retrieved calendar data is displayed correctly asap
  171. end;
  172.  
  173. function TCachedCalendar.WriteDatesToStream:boolean;
  174. var
  175.    stream:TfileStream;
  176.    DateInfo:TSpecialDateList;
  177.    c:longint;
  178. begin
  179.   // Check that if file not found for read then exception handled gracefully!
  180.   try
  181.      stream:=TFileStream.create(FConfigFile, fmCreate or fmOpenWrite);
  182.      for c:=0 to (FDateList.count-1) do
  183.        begin
  184.           DateInfo:=FDateList.items[c];
  185.           stream.WriteComponent(DateInfo);
  186.        end;
  187.      stream.free;
  188.      Result:=true;
  189.   except
  190.     on E:exception do
  191.        Result:=false;
  192.   end; // except
  193. end; // function
  194.  
  195. function TCachedCalendar.ReadDatesFromStream:boolean;
  196. var
  197.    stream:TfileStream;
  198.    ListComponent:TComponent;
  199. begin
  200.   // check that if file not found for read then exception handled gracefully!
  201.   try
  202.      FDateList.clear;
  203.      stream:=TfileStream.create(FConfigFile, fmopenread);
  204.      while not (stream.position = stream.size) do
  205.        begin
  206.          ListComponent:=stream.ReadComponent(nil);
  207.          if (ListComponent is TSpecialDateList) then
  208.            begin
  209.               FDateList.add((ListComponent as TSpecialDateList));
  210.            end;
  211.        end;
  212.      stream.free;
  213.      Result:=true;
  214.   except
  215.     on E:EFOpenError do
  216.        Result:=false;
  217.   end; // except
  218.   Invalidate;   // Ensure the newly retrieved calendar data is displayed correctly asap
  219. end;
  220.  
  221. // The following functions maintain the datasource and data links references
  222.  
  223. function TCachedCalendar.GetDateField:string;
  224. begin
  225.   GetDateField:=FDateFieldDataLink.FieldName;
  226. end;
  227.  
  228. function TCachedCalendar.GetDataSource:TDataSource;
  229. begin
  230.   GetDataSource:=FDateFieldDataLink.DataSource;
  231. end;
  232.  
  233. procedure TCachedCalendar.SetDateField(const theFieldName:string);
  234. begin
  235.   FDateFieldDataLink.FieldName:=theFieldName;
  236. end;
  237.  
  238. procedure TCachedCalendar.SetDataSource(theSource: TDataSource);
  239. begin
  240.   FDateFieldDataLink.DataSource:=theSource;
  241. end;
  242.  
  243. function TCachedCalendar.GetTextField:string;
  244. begin
  245.   GetTextField:=FTextFieldDataLink.FieldName;
  246. end;
  247.  
  248. procedure TCachedCalendar.SetTextField(const theFieldName:string);
  249. begin
  250.   FTextFieldDataLink.FieldName:=theFieldName;
  251. end;
  252.  
  253. procedure TCachedCalendar.DataChange(Sender:TObject);
  254. begin
  255.   if FDateFieldDataLink.Field = nil then
  256.     FUseCache:=ReadDatesFromStream;
  257. end;
  258.  
  259. procedure TCachedCalendar.Notification(AComponent: TComponent; Operation: TOperation);
  260. begin
  261.   // If the datasource is removed from the application, reset the data source reference
  262.   inherited Notification(AComponent, Operation);
  263.   if (Operation = opRemove) and (FDateFieldDataLink <> nil) and
  264.     (AComponent = DataSource) then DataSource := nil;
  265. end;
  266.  
  267. procedure TCachedCalendar.DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState);
  268. var
  269.   CellValue: string;
  270.   CellDate:TDatetime;
  271.   SearchDate:TSpecialDateList;
  272.   d:longint;
  273. begin
  274.    inherited;
  275.    CellValue:=CellText[Acol, ARow];
  276.    // Check to see if this cell represents a "special date" - ignoring the title row (Sun, Mon etc)
  277.    if ((CellValue<>'') and (ARow<>0)) then
  278.     begin
  279.      try
  280.        CellDate:=encodedate(Year, Month, StrToInt(CellValue));
  281.       // Look for the date amongst the list of special dates - though this is iterative, it's quick!
  282.        for d:=0 to (FDateList.count-1) do      // This is done as in-line code rather than a proc for performance
  283.        begin
  284.          SearchDate:=FDateList[d];
  285.          if (SearchDate.Date=CellDate) then
  286.            begin
  287.              // When a "red letter day" is found, paint its cell red!
  288.              Canvas.Brush.Color:=clRed;
  289.              Canvas.font.color:=clBlack;
  290.              with ARect, Canvas do
  291.                  TextRect(ARect, Left + (Right - Left - TextWidth(CellValue)) div 2,
  292.                  Top + (Bottom - Top - TextHeight(CellValue)) div 2, CellValue);
  293.              break; // leave the loop
  294.            end; // if
  295.        end; // for
  296.     except
  297.      on e:exception do showmessage(inttostr(arow))
  298.     end;
  299.   end; // if
  300. end;
  301.  
  302. procedure TCachedCalendar.Click;
  303. const
  304.   BUTTON_LEFT_OFFSET=10;
  305.   BUTTON_TOP_OFFSET=10;
  306. var
  307.   Point:TPoint;
  308.   CellValue: string;
  309.   CellDate:TDatetime;
  310.   SearchDate:TSpecialDateList;
  311.   d:longint;
  312. begin
  313.   // If the user clicks on a "red letter day", a popup menu appears showing the occasion
  314.   GetCursorPos(Point);
  315.   inherited Click;
  316.   CellValue:=CellText[Col,Row];
  317.   // Check to see if this cell represents a "special date" - ignoring the title row
  318.   if ((CellValue<>'') and (Row<>0)) then
  319.     begin
  320.      try
  321.        CellDate:=encodedate(Year, Month, StrToInt(CellValue));
  322.        // Look for the date amongst the list of special dates - though this is iterative, it's quick!
  323.        for d:=0 to (FDateList.count-1) do      // This is done as in-line code rather than a proc for performance
  324.        begin
  325.          SearchDate:=FDateList[d];
  326.          if (SearchDate.Date=CellDate) then
  327.            begin
  328.              if FDatePopupMenu<>nil
  329.                then FDatePopupMenu.free;
  330.              FDatePopupMenu:=TPopupMenu.Create(Self);
  331.              with FDatePopupMenu.Items do
  332.                begin
  333.                   Add(NewItem((FormatDateTime(LongDateFormat,SearchDate.Date)),0,False,true,nil,0,'PopupMenuItem1'));
  334.                   Add(NewLine);                       // Adds a separator bar
  335.                   Add(NewItem(SearchDate.Occasion,0,False,true,nil,0,'PopupMenuItem2'));
  336.                end; //with
  337.              FDatePopupMenu.Popup((Point.x+BUTTON_LEFT_OFFSET),(Point.y+BUTTON_TOP_OFFSET));
  338.              break; // leave the loop
  339.            end; // if
  340.        end; // for
  341.     except
  342.      on e:exception do showmessage(inttostr(Row))
  343.     end;
  344.   end; // if
  345. end;
  346.  
  347. end.
  348.